package day12;

import org.apache.hadoop.util.hash.Hash;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Before;
import org.junit.Test;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;

public class ESTest {

    /**
     * 1.拿到elasticSearch的连接
     *
     */
    private Client client;      // 注意找import org.elasticsearch.client.Client;包的Client

    @Before//这个@Before声明代表不管运行什么方法，，都会先把Before下面的给执行了。
    public void getClient() throws Exception {
        //配置请求 es 的信息
        //注意： es集群名称如果是elasticsearch，就不改配置了。但是我现在的es集群名称是es，所以要改配置。
        final HashMap<String,String> map = new HashMap<>();
        map.put("cluster.name", "es");
        final Settings.Builder settings = Settings.builder().put(map);
        // es的java的api提供的端口不是9200而是9300
        // 可以再添加多个节点，目的就是当 其中一个实例出现网络问题的时候，从而自动切换请求另一个实例。
        client = TransportClient.builder().settings(settings).build()
                .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("master"),9300))
                .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("slave1"),9300))
                .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("slave2"),9300));
    }

    /**
     * 2.使用json来创建文档并写入数据
     *
     */
    @Test
    public void createDoc_1() {
        // json字符串
        String source = "{" +
                "\"id\": \"1\", " +
                "\"title\":\"ElasticSearch是一个基于Lucene的搜索服务器\", " +
                "\"content\":\"它提供了一个分布式多用户能力的全文搜索引擎，基于RESTful web接口\"}";
        // 创建文档，定义索引index、文档类型type、主键id
        final IndexResponse indexResponse = client.prepareIndex("blog", "article", "1").setSource(source).get();// .get()也可以变为用execute().actionGet()方法。

        // 获取响应的信息
        System.out.println("index: " + indexResponse.getIndex());
        System.out.println("type: " + indexResponse.getType());
        System.out.println("id: " + indexResponse.getId());
        System.out.println("version: " + indexResponse.getVersion());
        System.out.println("是否创建成功: " + indexResponse.isCreated());
        client.close();
    }

    /**
     * 3.使用map插入数据
     *
     */
    @Test
    public void createDoc_2() {
        final HashMap<String, Object> source = new HashMap<>();
        source.put("id",2);
        source.put("title","elasticsearch");
        source.put("content","啊哈哈哈哈");

        final IndexResponse indexResponse = client.prepareIndex("blog", "article", "2").setSource(source).get();
        // 获取响应的信息
        System.out.println("index: " + indexResponse.getIndex());
        System.out.println("type: " + indexResponse.getType());
        System.out.println("id: " + indexResponse.getId());
        System.out.println("version: " + indexResponse.getVersion());
        System.out.println("是否创建成功: " + indexResponse.isCreated());
        client.close();
    }

    /**
     * 4.使用es的 帮助类插入数据
     *
     */
    @Test
    public void createDoc_3() throws Exception {
        final XContentBuilder source = XContentFactory.jsonBuilder()
                .startObject()
                .field("id", 3) //这个id是数据 字段id；而不是主键id！！！！
                .field("title", "Lucene的搜索服务器")
                .field("content", "啊实打实的")
                .endObject();

        client.prepareIndex("blog", "article", "3").setSource(source).get();//这里的3就是主键id即 _id
        client.close();
    }

    /**
     * 5.搜索文档，单个索引
     *
     */
    @Test
    public void getData_1() throws Exception {
        final GetResponse getResponse = client.prepareGet("blog", "article", "1").get();
        System.out.println(getResponse.getSourceAsString());
        client.close();
    }

    /**
     * 6.搜索文档，多个索引
     *
     */
    @Test
    public void getData_2() throws Exception {
        final MultiGetResponse multiGetItemResponses = client.prepareMultiGet()
                .add("blog", "article", "1")
                .add("blog", "article", "2")
                .get();

        // 遍历这多个数据：
        for (MultiGetItemResponse multiGetItemRespons : multiGetItemResponses) {
            GetResponse response = multiGetItemRespons.getResponse();
            // 判断该数据是否存在
            if (response.isExists())
                System.out.println(response.getSourceAsString());
        }

        client.close();
    }

    /**
     * 7.更新数据1
     *
     */
    @Test
    public void updateData_1() throws Exception {
        UpdateRequest request = new UpdateRequest();
        request.index("blog");
        request.type("article");
        request.id("1");
        request.doc(XContentFactory.jsonBuilder()
            .startObject()
            .field("id","1")
            .field("title","更新后：ElasticSearch是一个基于Lucene的搜索服务器")
            .field("content","更新后：它提供了一个分布式多用户能力的全文搜索引擎，基于RESTful web接口"));

        client.update(request).get();

        client.close();

    }

    /**
     * 8.更新数据2
     *
     */
    @Test
    public void updateData_2() throws Exception {
        client.update(
                new UpdateRequest("blog", "article", "2")
                        .doc(XContentFactory.jsonBuilder()
                                .startObject()
                                .field("id","2")
                                .field("title","更新后2：ElasticSearch")
                                .field("content","更新后2：222222222222222222222"))).get();

        client.close();
    }

    /**
     * 9.更新文档数据，设置一个查询的条件，查询id，如果查不到数据，则添加；如果查到，则更新。
     * 注意：设置的主键参数id在网页上就是_id（如果不设置的话会默认给一个数），查询它用的方法是.getId()；
     * 而我们自己设置的字段id就仅仅是字段，获取它可以用get("id")。注意不要不主键id弄混。
     *
     */
    @Test
    public void updateData_3() throws Exception {
        IndexRequest source = new IndexRequest("blog", "article", "4")//主键id
                .source(XContentFactory.jsonBuilder()
                        .startObject()
                        .field("id", "4")//自己的数据，字段id
                        .field("title", "hello")
                        .field("content", "fuhao")
                        .endObject());
        //设置更新的数据
        UpdateRequest upsert = new UpdateRequest("blog", "article", "4")
                .doc(XContentFactory.jsonBuilder()
                        .startObject()
                        .field("title", "ooooooooooooo")
                        .endObject())
                .upsert(source);

        client.update(upsert).get();

        client.close();
    }

    /**
     * 10.删除文档数据
     *
     */
    @Test
    public void deleteData() throws Exception {
        final DeleteResponse deleteResponse = client.prepareDelete("blog", "article", "4").get();
        client.close();
    }

    /**
     * 11.检索： SearchResponse，支持各种查询
     *
     */
    @Test
    public void Search() throws Exception {

        // 在没有用到分词器的情况下(es也自带了分词器)，是不是按照词来进行搜索的。
        // 比如：.setQuery(QueryBuilders.queryStringQuery方法，是按照单个字来搜索的。
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.queryStringQuery("更新"))//匹配所有数据字段中是否有更新两个字的。其实是按照单个字来进行搜索的。
                .get();

        // 获取数据的结果集对象，开启以获取命中次数
        SearchHits hits = searchResponse.getHits();

        System.out.println("查询的数据有多少条： " + hits.getTotalHits());

        // 遍历每条数据
        Iterator<SearchHit> it = hits.iterator();
        while (it.hasNext()){
            SearchHit searchHit = it.next();
            System.out.println("整条数据为：" + searchHit.getSourceAsString());
            // 打印每个字段的数据
            System.out.println("主键id: " + searchHit.getId());//主键id
            System.out.println("id: " + searchHit.getSource().get("id"));//字段id
            System.out.println("title: " + searchHit.getSource().get("title"));
            System.out.println("content: " + searchHit.getSource().get("content"));
        }

        client.close();
    }

    /**
     * 12.创建索引（ik分词器之后）？？
     *
     */
    @Test
    public void createIndex(){
        // 创建索引
        client.admin().indices().prepareCreate("blog2").get();
        // 删除索引
        // client.admin().indices().prepareDelete("blog2").get();
    }

    /**
     * 13.创建映射，指定分词器。？？？
     *
     */
    @Test
    public void createIndexMapping() throws Exception{
        XContentBuilder mappingBuilder = XContentFactory.jsonBuilder()
                .startObject()
                    .startObject("article")
                        .startObject("properties")
                            .startObject("id")
                                .field("type", "integer").field("store", "yes")
                            .endObject()
                            .startObject("title")
                                .field("type", "string").field("store", "yes").field("analyzer", "ik")
                            .endObject()
                            .startObject("content")
                                .field("type", "string").field("store", "yes").field("analyzer", "ik")
                            .endObject()
                        .endObject()
                    .endObject()
                .endObject();

        PutMappingRequest source = Requests.putMappingRequest("blog2")
                .type("article")
                .source(mappingBuilder);

        client.admin().indices().putMapping(source).get();

        client.close();
    }

    /**
     * 打印查询响应信息方法
     *
     */
    public void getResponse(SearchResponse searchResponse) {
        // 获取数据的结果集对象，开启以获取命中次数
        SearchHits hits = searchResponse.getHits();

        System.out.println("查询的数据有多少条： " + hits.getTotalHits());

        // 遍历每条数据
        Iterator<SearchHit> it = hits.iterator();
        while (it.hasNext()){
            SearchHit searchHit = it.next();
            System.out.println("整条数据为：" + searchHit.getSourceAsString());
            // 打印每个字段的数据
            System.out.println("主键id: " + searchHit.getId());//主键id
            System.out.println("id: " + searchHit.getSource().get("id"));//字段id
            System.out.println("title: " + searchHit.getSource().get("title"));
            System.out.println("content: " + searchHit.getSource().get("content"));
        }
    }

    /**
     * 14.查询：查询所有 : matchAllQuery
     *
     */
    @Test
    public void queryAll() {
        SearchResponse searchResponse = client.prepareSearch("blog")//哪个数据库
                .setTypes("article")//哪个表
                .setQuery(QueryBuilders.matchAllQuery())//怎么查
                .get();
        this.getResponse(searchResponse);//这是自己写的一个函数，遍历所有结果的函数

        client.close();
    }


    /**
     * 15.查询：解析查询字符串
     *
     */
    @Test
    public void queryString() {
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.queryStringQuery("搜索").field("content").field("title")) // 查询content和title字段是否含有“搜索"二字。
                .get();
        this.getResponse(searchResponse);

        client.close();
    }

    /**
     * 16.查询：通配符查询？？？？？？？？？？有点问题？还是不太会用！！！。也不常用。通常数据库都不建议用模糊查询
     * 通配符：用与模糊搜索，常用的有 ？和 *  这两种。其中？为仅代表单个字符串，且此单字字必须存在； *可以代表任意一个字符串
     */
    @Test
    public void wildcardQuery() {
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.wildcardQuery("content", "搜*"))//这就是找content字段内容有 搜这个字的数据。问题：我这里写搜索就查不到了？？？怎么回事？？
                .get();

        this.getResponse(searchResponse);
        client.close();
    }

    /**
     * 好像我这里所有的term查询都有问题！！！！！！！！！
     * 17.查询：词条查询。???。奇怪？为什么，我这里的词条查询不起作用，明明title是有这个更新两个字的。-> 怀疑是我的ik分词有问题！！！
     *  match是分词匹配的，会将条件中的一句话切分为多个单词，只需其中一个单词匹配，就命中，然后根据Lucence的评分系统计算评分；
     *  而term是严格全句匹配的，但是有个条件：使用term要确定的这个字段是否“被分析”(analyzed)，默认的字符串是被分析的。
     *  根据实际情况选择到底是match还是term
     *
     * 原来是我这里用了term，但是这个字段没有“被分析”(analyzed)
     */
    @Test
    public void termQuery() throws Exception{
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.termQuery("title", "更新"))//也可匹配多个，只要有一个满足即可termQuery("title", "更新","搜索"))
                .get();
        this.getResponse(searchResponse);
        client.close();
    }


    /**
     * 18.查询：字段匹配查询???????
     *
     */
    @Test
    public void fieldMatchQuery() throws Exception{
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                // 该方法需要给一个匹配度的值，这样可以给一个不完整的词的查询
                //.setQuery(QueryBuilders.matchQuery("title", "更新").analyzer("ik").fuzziness(0.1))
                // 给某个词的前缀也可以查询
                //.setQuery(QueryBuilders.matchPhrasePrefixQuery("title","服"))
                // 指定多字段进行搜索
                .setQuery(QueryBuilders.multiMatchQuery("搜索","title","content"))//第一个参数是要搜索的词，后面的参数是指定哪个字段，
                .get();
        this.getResponse(searchResponse);
        client.close();
    }

    /**
     * 19.查询：id查询
     *
     */
    @Test
    public void idQuery() throws Exception{
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.idsQuery().ids("2","3"))
                .get();
        this.getResponse(searchResponse);
        client.close();
    }

    /**
     * 20.查询：模糊查询:相似度查询
     *
     */
    @Test
    public void fuzzyQuery() throws Exception{
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.fuzzyQuery("title", "Lucene"))
                .get();
        this.getResponse(searchResponse);
        client.close();
    }

    /**
     * 21.查询：范围查询
     *
     */
    @Test
    public void rangeQuery(){
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.rangeQuery("id").gte(1).lte(3))
                .get();
        this.getResponse(searchResponse);
        client.close();
    }

    /**
     * 22.查询：布尔查询，需要和其他查询结合在一起
     *
     */
    @Test
    public void boolQuery(){
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("title","搜索"))//好像我这里所有与termQuery有关的都查不到，term有问题？？？？？？
                        .should(QueryBuilders.rangeQuery("id").from(1).to(3))
                )
                .get();
        this.getResponse(searchResponse);
        client.close();
    }

    /**
     * 23.查询：排序查询，需要指定升序还是降序
     *
     */
    @Test
    public void sortQuery(){
        SearchResponse searchResponse = client.prepareSearch("blog")
                .setTypes("article")
                .setQuery(QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("title","搜索"))//好像我这里所有与termQuery有关的都查不到，term有问题？？？？？？
                        .should(QueryBuilders.rangeQuery("id").from(1).to(3)))
                .addSort("id", SortOrder.DESC)
                .get();
        this.getResponse(searchResponse);
        client.close();
    }

}
